import serial
import time
import logging
import random
import datetime
from collections import OrderedDict
from func.utils import *
import string


# 配置日志,避免重复配置
# 在文件顶部添加日志配置
logging.basicConfig(
    level=logging.INFO,
    # format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('m600_communicator.log'),
        logging.StreamHandler()
    ]
)

class DsrcFrame:
    """DSRC帧的公共类
    该类定义了DSRC帧的基本结构和方法,包括帧头、帧尾、校验等。
    子类可以根据具体的DSRC协议扩展这些方法。
    STX	帧开始标志,取值为55AAH;
    RSCTL	帧序列号,1个字节;
    (1) 帧序列号的低半字节为0~7,高半字节保留;
    (2) 帧序列号每次加一,用于标识每一次的通信。
    LEN	DATA域的长度,2字节(注意长度不含CMD字节)
    DATA	CMD	指令类型(常见的F0,F1… E0,E1等)	具体定义见本文档第4部分
        DATA	发送的数据包 	
    BCC	异或校验值,从RSCTL到DATA所有字节的异或值; 
    """
    STX = 0x55AA

    def __init__(self):
        self.RSCTL = 0x00  # 初始帧序列号
        self.LEN = 0x00  # 初始数据长度
        self.CMD = 0xF0  # 初始指令类型
        self.DATA = b''  # 初始数据
        self.BCC = 0x00  # 初始校验值

    def _increment_rsctl(self):
        """递增帧序列号,低半字节在 0 - 7 循环"""
        self.RSCTL = ((self.RSCTL & 0xF0) | ((self.RSCTL + 1) & 0x07))

    def _build_frame(self):
        """构建完整的DSRC帧"""
        self.LEN = len(self.DATA)
        frame = bytearray()
        frame.extend(self._split_stx())
        frame.append(self.RSCTL)
        frame.extend(self._split_len())
        frame.append(self.CMD)
        frame.extend(self.DATA)
        self._increment_rsctl()  # 构建帧后递增序列号
        self.BCC = get_bcc(frame[2:])
        frame.append(self.BCC)
        return frame

    def calc(self, data: bytes) -> int:
        """计算CRC16校验值"""
        crc = 0x0000
        for b in data:
            crc = ((crc << 8) & 0xFFFF) ^ self.crctable[((crc >> 8) ^ b) & 0xFF]
        logging.info(f"CRC16校验码: {crc}")
        return crc

    def calc_with_offset(self, data: bytes, offset: int, size: int) -> int:
        """从指定偏移量开始计算CRC16"""
        crc = 0x0000
        for i in range(size):
            b = data[i + offset]
            crc = ((crc << 8) & 0xFFFF) ^ self.crctable[((crc >> 8) ^ b) & 0xFF]
        logging.info(f"CRC16校验码: {crc}")  # 打印CRC16校验码的十六进制表示,每个字节之间用空格分隔,大写字母表示
        return crc

    def calc_with_initial(self, crc: int, data: bytes, size: int) -> int:
        """使用初始值计算CRC16"""
        for i in range(size):
            b = data[i]
            crc = ((crc << 8) & 0xFFFF) ^ self.crctable[((crc >> 8) ^ b) & 0xFF]
        logging.info(f"CRC16校验码: {crc}")
        return crc

    def _split_stx(self):
        """拆分 STX 为高字节和低字节"""
        return [(self.STX >> 8) & 0xFF, self.STX & 0xFF]

    def _split_len(self):
        """拆分 LEN 为高字节和低字节"""
        return [(self.LEN >> 8) & 0xFF, self.LEN & 0xFF]


class M600Communicator:
    def __init__(self, port='COM8', baudrate=115200, timeout=2):
        """
        初始化串口连接
        :param port: 串口号(默认COM7)
        :param baudrate: 波特率(115200)
        :param timeout: 超时时间(秒,默认1)
        """

        self.serial = serial.Serial(
            port=port,
            baudrate=baudrate,
            timeout=timeout
        )
        time.sleep(1)  # 等待串口初始化
        logging.info(f"Connected to {port} at {baudrate} baud")
        self.dsrc_frame = DsrcFrame()
        self.obu_mac_id = None  # 存储OBU的MAC地址
        self.profile = '00' #信道号,范围0-1
        self.RndRSU = 'F13F410C8CDA0000' #随机数
        self.psamCardId = '5C8339197585' #PSAM卡号
        self.read_0015 = "00B09500" # 读取0015文件 2B长度，返回卡类型（1字节），IC卡ID（1字节），发卡运营商代码（2遍共8字节）
        self.read_0019 = "00B201CC" # 读取0019文件 2B长度，返回0019内容
        self.read_0002 = "805C0002" # 读取0002文件 04长度，返回卡余额
        self.write_0019 = '80DCAAC82BAA29' # 写0019文件 0019内容
        
    def send_command(self, hex_str, need_response=True):
        """
        发送十六进制命令到M600设备
        优化点:
        1. 增加十六进制字符串验证
        2. 增强错误处理
        :param hex_str: 十六进制字符串(如"55 AA 01 00")
        :param need_response: 是否需要响应
        :return: 设备响应或None
        """
        if self.serial is None:
            logging.error("Serial connection is not initialized.")
            return None
            
        try:
            hex_bytes = bytes.fromhex(hex_str.replace(" ", ""))
            start_time = time.time()
            self.serial.write(hex_bytes)
            end_time = time.time()
            delta_ms = get_delta_ms(start_time, end_time)
            logging.info(f"[{delta_ms:07.2f}] Req: {hex_str}")
            if need_response:
                return self.read_55AA()
        except ValueError as e:
            logging.error(f"无效的十六进制字符串: {hex_str}, 错误: {e}")
            return None
        except Exception as e:
            logging.error(f"发送命令时发生错误: {e}")
            return None

    def read_55AA(self):
        """
        读取55AA格式的响应帧
        优化点:
        1. 精确计算总长度(6+data长度)
        2. 增强超时处理
        3. 优化日志记录
        :return: 十六进制格式的响应字符串或空字符串
        """
        response = b''
        try:
            start_time = time.time()
            
            # 1. 读取7字节帧头(包含STX、RSCTL、LEN和CMD)
            header = b''
            while len(header) < 7 and (time.time() - start_time) < self.serial.timeout:
                chunk = self.serial.read(7 - len(header))
                if chunk:
                    header += chunk
            
            # 验证帧头完整性
            if len(header) < 7:
                logging.error(f"帧头不完整,仅收到{len(header)}字节")
                return ""
                
            # 2. 验证55AA起始标志
            if header[0] != 0x55 or header[1] != 0xAA:
                logging.error(f"无效帧头: {header[:2].hex(' ').upper()}")
                return ""
                
            # 3. 计算数据长度和总长度
            data_len = (header[3] << 8) | header[4]
            total_len = 7 + data_len  # STX(2)+RSCTL(1)+LEN(2)+CMD(1)+DATA+BCC(1)
            
            # 4. 读取剩余数据(如果data_len > 0)
            if data_len > 0:
                remaining_data = b''
                remaining_bytes = total_len - 7
                while len(remaining_data) < remaining_bytes and (time.time() - start_time) < self.serial.timeout:
                    chunk = self.serial.read(remaining_bytes - len(remaining_data))
                    if chunk:
                        remaining_data += chunk
                
                if len(remaining_data) < remaining_bytes:
                    logging.error(f"数据不完整,期望{remaining_bytes}字节,收到{len(remaining_data)}字节")
                    return ""
                    
                response = header + remaining_data
            else:
                response = header
            
            # 5. 校验数据完整性
            if len(response) != total_len:
                logging.error(f"帧长度不匹配,期望{total_len}字节,收到{len(response)}字节")
                return ""
                
            # 6. BCC校验
            if not self._verify_bcc(response):
                logging.error("BCC校验失败")
                return ""
            end_time = time.time()
            delta_ms = get_delta_ms(start_time, end_time)
            logging.info(f"[{delta_ms:07.2f}] Ack: {response.hex(' ').upper()}")
                
            return response.hex(" ").upper()
            
        except Exception as e:
            logging.error(f"读取响应时发生错误: {str(e)}")
            return ""

    def _read_response(self):
        """
        读取设备响应(内部方法)
        修改为: 先读取7字节帧头,验证55AA起始标志
        根据第4-5字节的数据长度计算总长度(6+数据长度)
        读取完整帧后返回或超时退出
        :return: 解码后的响应字符串或原始字节数据
        """
        response = b''
        try:
            start_time = time.time()
            # 先读取7字节帧头
            header = b''
            while len(header) < 7 and (time.time() - start_time) < self.serial.timeout:
                byte = self.serial.read(1)
                if byte:
                    header += byte
            
            if len(header) < 7:
                logging.error("Timeout while reading frame header")
                return response.hex().upper()
                
            # 验证起始标志55AA
            if header[0] != 0x55 or header[1] != 0xAA:
                logging.error(f"Invalid frame header: {header.hex(' ').upper()}")
                return response.hex().upper()
                
            # 获取数据长度(第4-5字节)
            data_len = (header[3] << 8) | header[4]
            total_len = 7 + data_len  # STX(2)+RSCTL(1)+LEN(2)+CMD(1)+DATA+BCC(1)
            
            # 读取剩余数据
            remaining_bytes = total_len - 7
            if remaining_bytes > 0:
                remaining_data = b''
                while len(remaining_data) < remaining_bytes and (time.time() - start_time) < self.serial.timeout:
                    byte = self.serial.read(1)
                    if byte:
                        remaining_data += byte
                
                if len(remaining_data) < remaining_bytes:
                    logging.error(f"Timeout while reading frame data, expected {remaining_bytes} bytes, got {len(remaining_data)}")
                    return response.hex().upper()
                    
                response = header + remaining_data
            
            logging.info(f"Received response: {response.hex(' ').upper()}")
            return response.hex(" ").upper()
        except Exception as e:
            logging.error(f"Error reading response: {e}")
            return response.hex().upper()

    def close(self):
        """关闭串口连接"""
        if self.serial and self.serial.is_open:
            self.serial.close()
            logging.info("M600T Serial connection closed")

    def __del__(self):
        """析构函数自动关闭连接"""
        self.close()

    def get_current_unixtime(self):
        """
        获取当前Unix时间戳(32位十六进制字符串)
        :return: 8字符的十六进制字符串(如"67EBAE58")
        """
        return f"{int(time.time()):08X}"

    def get_unixtime_bytes(self):
        """
        获取当前Unix时间戳的字节字符串形式
        :return: 以空格分隔的十六进制字节字符串
        """
        unixtime = self.get_current_unixtime()
        return ' '.join([unixtime[i:i + 2] for i in range(0, 8, 2)])

    def InitRSU(self, bst_interval='0A', interval='0A', tx_power='10',
                channel_id='01', bst_retry='64', retry='32', carrier='00 00'):
        """
        生成PowerFrame命令帧
        :param bst_interval: BST间隔(默认0A)
        :param interval: 间隔时间(默认0A)
        :param tx_power: 发射功率(默认1F)
        :param channel_id: 通道ID(默认01)
        :param bst_retry: BST重试次数(默认64)
        :param retry: 重试次数(默认32)
        :param carrier: 载波信息(默认00 00)
        :return: 完整的PowerFrame命令字符串
        """
        logging.info("--------------初始化RSU------------")
        unixtime_bytes = self.get_unixtime_bytes()
        data_str = f"{unixtime_bytes} {bst_interval} {interval} {tx_power} {channel_id} {bst_retry} {retry} {carrier}"
        self.dsrc_frame.CMD = 0xF0
        self.dsrc_frame.DATA = bytes.fromhex(data_str)
        self.profile = channel_id
        #print('profile=', self.profile)
        frame = self.dsrc_frame._build_frame()
        response =  self.send_command(frame.hex(" ").upper())
        return response

    def SendBST(self, MAC='FFFFFFFF', control='50', llc='03', pdu_num = '91', action_req = 'C0', BeaconID = '02112233',
                profile = 'FF', mand_applications = '01', option_indicator = '41', icc_trans_mode = '87', container_type = '29',
                option = 'F0', sys_info_file = '1B', length0002 = '', length0012 = '', length0015 = '', length0019 = '',
                profile_list = '00', max_retries=10, retry_interval=0.5):
        """
        生成BST命令帧
        :param MAC: 目标MAC地址(默认FFFFFFFF)
        :param control: 控制字段(默认50)
        :param llc: LLC字段(默认03)
        :param pdu_num: 段字头(默认91)
        :param action_req: BST字段(默认C0)
        :param BeaconID: BeaconID字段(默认021C4050)
        :param unixtime: Unix时间戳(默认当前时间)
        :param profile: 配置文件(默认01)
        :param mand_applications: 必须应用程序(默认01)
        :param option_indicator: 可选应用程序(默认41)
        :param icc_trans_mode: ICC传输模式(默认87)
        :param container_type: 容器类型(默认29)
        :param option: 选项(默认80)
        :param sys_info_file: 系统信息文件(默认1B)
        :param length0002: 长度0002(默认0004)
        :param length0012: 长度0012(默认空)
        :param length0015: 长度0015(默认空)
        :param length0019: 长度0019(默认空)
        :param profile_list: 配置文件列表(默认00)
        :param max_retries: 最大重试次数(默认3)
        :param retry_interval: 重试间隔(秒,默认1)
        :return: (响应数据, 解析后的VST信息)
        """
        timestamp = int(time.time() * 1000)
        BeaconID = f"02{hash(str(timestamp)) & 0xFFFFFF:06X}"
        logging.info(f"BeaconID: {BeaconID}")
        logging.info("--------------开始发送BST------------")
        logging.info(f"目标MAC: {MAC}, 重试次数: {max_retries}, 重试间隔: {retry_interval}s")
        
        unixtime = self.get_unixtime_bytes()
        option_byte = int(option, 16)
        
        # 计算option
        if length0002 == "":
            option_byte &=  0x7F
        if length0012 == "":
            option_byte &= 0xBF
        if length0015 == "":
            option_byte &= 0xDF
        if length0019 == "":
            option_byte &= 0xEF
        option = f"{option_byte:02X}"

        if profile != "00" and profile != "01":
            profile = self.profile
            
        data_parts = [
            MAC, control, llc, pdu_num, action_req, BeaconID, unixtime, 
            profile, mand_applications, option_indicator, icc_trans_mode, 
            container_type, option, sys_info_file, length0002, 
            length0012, length0015, length0019, profile_list
        ]
        data = ' '.join(filter(None, data_parts))
        self.dsrc_frame.CMD = 0xF1
        self.dsrc_frame.DATA = bytes.fromhex(data)
        frame = self.dsrc_frame._build_frame()
        
        last_error = None
        for attempt in range(max_retries + 1):
            try:
                logging.info(f"尝试 {attempt + 1}/{max_retries + 1} 发送BST...")
                response = self.send_command(frame.hex(" ").upper())
                if response == "":
                    raise Exception("BST发送未收到响应")
                parse_VST_str = self.parse_VST_str(response)
                self.obu_mac_id = parse_VST_str.get('MAC地址')
                if len(self.obu_mac_id) > 6:
                    self.obu_mac_id = parse_VST_str.get('MAC地址')
                    logging.info(f"BST发送成功, 响应MAC: {self.obu_mac_id}")
                    return response
                else:
                    raise Exception("解析VST响应失败")
            except Exception as e:
                last_error = e
                logging.error(f"BST发送失败(尝试 {attempt + 1}): {str(e)}")
                if attempt < max_retries:
                    time.sleep(retry_interval)
        
        logging.error(f"BST发送失败, 已达最大重试次数: {str(last_error)}")
        raise Exception(f"BST发送失败: {str(last_error)}")
    
    def PrintOBUMac(self):
        print('OBU mac=',self.obu_mac_id)
    
    def GetSecure(self, ConfirmMode = '01', mac = '02112233', control = '40', llc = 'F7', pdu_num = '91', action_req = '05', 
                DID = '01', action_type = '00', container_type = '14', KeyIDforEncryptOp = '80', 
                FID='01', offset='0000', length='3B', RndRSU='', KeyIDforAuthen='00', KeyIDforEncrypt='42'):
        """
        生成Secure命令帧
        :param ConfirmMode: 确认模式(默认01)
        :param mac: 目标MAC地址(默认FFFFFFFF)
        :param control: 控制字段(默认40)
        :param llc: LLC字段(默认F7)
        :param pdu_num: 段字头(默认91)
        :param action_req: Secure字段(默认05)
        :param DID: 数据标识(默认01)
        :param action_type: 操作类型(默认00)
        :param container_type: 容器类型(默认14)
        :param KeyIDforEncryptOp: 加密操作
        :param FID: 文件标识(默认01)
        :param offset: 偏移量(默认0000)
        :param length: 长度(默认3B)
        :param RndRSU: RndRSU字段(默认F13F410C8CD
        :param KeyIDforAuthen: 认证密钥ID(默认00)
        :param KeyIDforEncrypt: 加密密钥ID(默认42)
        :return: 完整的Secure命令返回字符串
        """
        logging.info("--------------获取Secure------------")
        mac = self.obu_mac_id
        RndRSU = self.RndRSU
        data = f"{ConfirmMode} {mac} {control} {llc} {pdu_num} {action_req} {DID} {action_type} {container_type} {KeyIDforEncryptOp} {FID} {offset} {length} {RndRSU} {KeyIDforAuthen} {KeyIDforEncrypt}"
        self.dsrc_frame.CMD = 0xF2
        self.dsrc_frame.DATA = bytes.fromhex(data)
        frame = self.dsrc_frame._build_frame()
        response =  self.send_command(frame.hex(" ").upper())
        return response
        
    def ParseSecureResponse(self, response):
        """
        解析Secure命令响应数据
        :param response: Secure命令响应字符串(格式: 55 AA 03 00 66 E2 00 69 FF FF 1A E0...)
        :return: 解析后的字段字典或None(解析失败时)
        返回字段说明:
        - Header: 帧头(2字节)
        - MAC: 消息认证码(4字节)
        - Control: 控制字段(1字节)
        - LLC: 逻辑链路控制(1字节)
        - State: 状态字段(1字节)
        - CTRL: 控制字段(1字节)
        - ActionResponse: 动作响应(1字节)
        - DID: 设备ID(1字节)
        - Container: 容器类型(1字节)
        - FID: 文件ID(1字节)
        - File: 文件内容(变长)
        - Authenticator: 认证码(8字节)
        - ReturnStatus: 返回状态(1字节)
        - Length: 总字节长度
        """
        try:
            if not response or not isinstance(response, str):
                raise ValueError("响应数据为空或类型错误")
                
            # 移除空格并检查长度
            hex_str = response.replace(" ", "")
            if len(hex_str) < 32:
                raise ValueError(f"响应数据长度不足32字节: {len(hex_str)//2}字节")
            if len(hex_str) % 2 != 0:
                raise ValueError(f"响应数据长度无效: {len(hex_str)}字符")
                
            # 转换为字节并验证起始标志
            data = bytes.fromhex(hex_str)
            if data[0] != 0x55 or data[1] != 0xAA:
                raise ValueError("无效的响应起始标志")
            
            # 验证关键字段长度
            if len(hex_str) < 32:
                raise ValueError("响应数据不包含完整字段")
                
            # 解析各字段并验证
            result = {
                "Header": hex_str[0:4],
                "MAC": hex_str[14:22],
                "Control": hex_str[22:24],
                "LLC": hex_str[24:26],
                "State": hex_str[26:28],
                "CTRL": hex_str[28:30],
                "ActionResponse": hex_str[30:32],
                "DID": hex_str[32:34],
                "Container": hex_str[34:36],
                "FID": hex_str[36:38],
                "File": hex_str[38:-20],
                "Authenticator": hex_str[-20:-4],
                "ReturnStatus": hex_str[-4:-2]
            }
            
            # 验证关键字段
            if len(result["MAC"]) != 8:
                raise ValueError(f"MAC字段长度错误: {result['MAC']}")
            if len(result["Authenticator"]) != 16:
                raise ValueError(f"Authenticator字段长度错误: {result['Authenticator']}")
            if len(result["ReturnStatus"]) != 2:
                raise ValueError(f"ReturnStatus字段长度错误: {result['ReturnStatus']}")
            
            # 添加长度验证
            result["Length"] = len(hex_str)//2
            
            logging.debug(f"Secure响应解析结果: {result}")
            return result
            
        except ValueError as ve:
            logging.error(f"Secure响应数据格式错误: {str(ve)}")
            return None
        except Exception as e:
            logging.error(f"解析Secure响应失败: {str(e)}", exc_info=True)
            return None   
    
    def TransferChannel(self, ConfirmMode = '01', mac = '02112233', control = '40', llc = '77', pdu_num = '91', action_req = '05', 
                DID = '01', action_type = '03', container_type = '18', channelID = '01', Apdus=None):
        """
        透传通道操作
        :param ConfirmMode: 确认模式(默认01)
        :param mac: MAC地址(默认02112233)
        :param control: 控制字段(默认40)
        :param llc: LLC字段(默认77)
        :param pdu_num: PDU编号(默认91)
        :param action_req: 请求动作(默认05)
        :param DID: 设备ID(默认01)
        :param action_type: 动作类型(默认03)
        :param container_type: 容器类型(默认18)
        :param channelID: 通道ID(默认01)
        :param Apdus: 单个APDU指令字符串或多个APDU指令列表
        :return: 响应数据
        """
        logging.info(f"--------------透传指令[{Apdus}]------------")
        mac = self.obu_mac_id
        
        # 处理单个字符串或列表
        if isinstance(Apdus, str):
            Apdus = [Apdus]
        elif Apdus is None:
            Apdus = []
            
        ApduList = len(Apdus)
        processed_apdus = []
        for apdu in Apdus:
            # 去掉多余的空格
            apdu = apdu.replace(" ", "")
            # 计算APDU指令长度(字节数,每个十六进制字符占半字节)
            apdu_length = len(apdu) // 2
            len_hex = f"{apdu_length:02X}"
            # 1字节长度和原APDU内容
            processed_apdus.append(f"{len_hex} {apdu}")
        apdu_str = ' '.join(processed_apdus)
        data = f"{ConfirmMode} {mac} {control} {llc} {pdu_num} {action_req} {DID} {action_type} {container_type} {channelID} {ApduList:02X} {apdu_str}"
        try:
            self.dsrc_frame.CMD = 0xF2
            self.dsrc_frame.DATA = bytes.fromhex(data)
            frame = self.dsrc_frame._build_frame()
            response = self.send_command(frame.hex(" ").upper())
            return response
        except ValueError as e:
            logging.error(f"十六进制数据验证失败: {str(e)}")
            raise Exception(f"十六进制数据验证失败: {str(e)}")

    def ParseTransferResponse(self, response, isPrint = False):
        """
        解析Transfer命令响应数据
        :param response: Secure命令响应字符串(格式: 55 AA 03 00 66 E2 00 69 FF FF 1A E0...)
        :return: 解析后的字段字典或None(解析失败时)
        返回字段说明:
        - Header: 帧头(2字节)
        - MAC: 消息认证码(4字节)
        - Control: 控制字段(1字节)
        - LLC: 逻辑链路控制(1字节)
        - State: 状态字段(1字节)
        - CTRL: 控制字段(1字节)
        - ActionResponse: 动作响应(1字节)
        - DID: 设备ID(1字节)
        - Container: 容器类型(1字节)
        - FID: 文件ID(1字节)
        - ChannelRs: 
        - ChannelID: 
        - ApduList: 
        - Apdu0Len: 第一个APDU指令长度(2字节)
        - Apdu0: 第一个APDU指令内容
        - Apdu1Len: 第二个APDU指令长度(2字节)
        - Length: 总字节长度
        """
        try:
            if not response or not isinstance(response, str):
                raise ValueError("响应数据为空或类型错误")
                
            # 移除空格并检查长度
            hex_str = response.replace(" ", "")
            if len(hex_str) < 32:
                raise ValueError(f"响应数据长度不足32字节: {len(hex_str)//2}字节")
            if len(hex_str) % 2 != 0:
                raise ValueError(f"响应数据长度无效: {len(hex_str)}字符")
                
            # 转换为字节并验证起始标志
            data = bytes.fromhex(hex_str)
            if data[0] != 0x55 or data[1] != 0xAA:
                raise ValueError("无效的响应起始标志")
            
            # 验证关键字段长度
            if len(hex_str) < 32:
                raise ValueError("响应数据不包含完整字段")
                
            # 解析各字段并验证
            # 验证数据长度是否足够
            if len(hex_str) < 40:
                raise ValueError(f"响应数据长度不足,期望至少40字节,实际收到{len(hex_str)//2}字节")

            result = {
                "Header": hex_str[0:4],
                "MAC": hex_str[14:22],
                "Control": hex_str[22:24],
                "LLC": hex_str[24:26],
                "State": hex_str[26:28],
                "CTRL": hex_str[28:30],
                "ActionResponse": hex_str[30:32],
                "DID": hex_str[32:34],
                "ChannelRs": hex_str[34:36],
                "ChannelID": hex_str[36:38],
                "ApduList": hex_str[38:40],
                "ReturnStatus": hex_str[-4:-2]
            }
            # 解析多个APDU数据
            apdu_count = int(hex_str[38:40], 16)
            apdu_data = {}
            current_pos = 40
            # 验证APDU数据长度
            remaining_len = len(hex_str) - current_pos
            if remaining_len < apdu_count * 4:
                raise ValueError(f"APDU数据长度不足,期望至少{apdu_count*4}字节,实际剩余{remaining_len//2}字节")
            for i in range(apdu_count):
                    if current_pos + 2 > len(hex_str):
                        raise ValueError(f"APDU长度字段超出数据范围")
                    apdu_len = int(hex_str[current_pos:current_pos+2], 16)
                    
                    if current_pos + 2 + apdu_len*2 > len(hex_str):
                        raise ValueError(f"APDU数据超出范围,期望长度{apdu_len*2}字节,实际剩余{(len(hex_str)-current_pos-2)//2}字节")
                    apdu_data[f"Sw12_{i}"] = hex_str[current_pos+2+apdu_len*2 -4 :current_pos+2+apdu_len*2]
                    apdu_data[f"Apdu{i}_Len"] = hex_str[current_pos:current_pos+2]
                    if apdu_data[f"Sw12_{i}"] != "9000":
                        apdu_data[f"Apdu_{i}"] = hex_str[current_pos+2:current_pos+2+apdu_len*2]
                    else:
                        apdu_data[f"Apdu_{i}"] = hex_str[current_pos+2:current_pos+2+apdu_len*2 - 4]
                    current_pos += 2 + apdu_len*2
            result.update(apdu_data)
            
            # 验证关键字段
            if len(result["MAC"]) != 8:
                raise ValueError(f"MAC字段长度错误: {result['MAC']}")
            
            # 添加长度验证
            result["Length"] = len(hex_str)//2
            if isPrint:
                logging.info(f"Transfer响应解析结果: {result}")
            return result
            
        except ValueError as ve:
            logging.error(f"Transfer响应数据格式错误: {str(ve)}")
            return None
        except Exception as e:
            logging.error(f"解析Transfer响应失败: {str(e)}", exc_info=True)
            return None    

    def Parse_0015(self, response, isPrint = False):
        """
        解析0015响应数据
        :param response: 响应数据字符串(格式: 55 AA 03 00 66 E2 00 69 FF FF 1A E0...)
        :return: 解析后的字段字典或None(解析失败时)
        返回字段说明:
        - 发卡方标识: 16字节
        - 卡片类型: 2字节
        - 卡片版本号: 2字节
        - 卡片网格编号: 4字节
        - 用户卡内部编号: 8字节
        - 启用时间: 8字节
        - 到期时间: 8字节
        - 车牌号码: 16字节
        - 用户类型: 2字节
        - 车牌颜色: 2字节
        - 车型: 2字节
        - 预留: 6字节
        - 车牌号码BCD: 车牌号码的BCD码
        - Length: 总字节长度
        """
        try:
            if not response or not isinstance(response, str):
                raise ValueError("响应数据为空或类型错误")
                
            # 移除空格并检查长度
            hex_str = response.replace(" ", "")
            if len(hex_str) < 50:
                raise ValueError(f"响应数据长度不足43字节: {len(hex_str)//2}字节")
            if len(hex_str) % 2 != 0:
                raise ValueError(f"响应数据长度无效: {len(hex_str)}字符")
            
            # 验证关键字段长度
            if len(hex_str) < 50:
                raise ValueError("响应数据不包含完整字段")
                
            # 解析各字段并验证
            result = {
                "发卡方标识": hex_str[0:16],
                "卡片类型": hex_str[16:18],
                "卡片版本号": hex_str[18:20],
                "卡片网格编号": hex_str[20:24],
                "用户卡内部编号": hex_str[24:40],
                "启用时间": hex_str[40:48],
                "到期时间": hex_str[48:56],       
                "车牌号码": hex_str[56:80],
                "用户类型": hex_str[80:82],
                "车牌颜色": hex_str[82:84],           
                "车型": hex_str[84:86],
                "预留": hex_str[86:92],
                "预留": hex_str[92:],
            }
            try:
                # 解析车牌号码
                bytes_obj = bytes.fromhex(hex_str[56:80].replace(" ", ""))
                string = bytes_obj.decode('gbk')
                result["车牌号码BCD"] =  string
            except Exception as e:
                logging.error(f"车牌号码解析失败: {str(e)}")
                result["车牌号码BCD"] = "解析失败"
            # 添加长度验证
            result["Length"] = len(hex_str)//2
            if isPrint:
                logging.info(f"0015解析结果: {result}")
            return result
            
        except ValueError as ve:
            logging.error(f"0015响应数据格式错误: {str(ve)}")
            return None
        except Exception as e:
            logging.error(f"解析0015响应失败: {str(e)}", exc_info=True)
            return None  

    def Parse_0019(self, response, isPrint = False):
        """
        解析0019响应数据
        :param response: 响应数据字符串(格式: 55 AA 03 00 66 E2 00 69 FF FF 1A E0...)
        :return: 解析后的字段字典或None(解析失败时)
        返回字段说明:

        - Length: 总字节长度
        """
        try:
            if not response or not isinstance(response, str):
                raise ValueError("响应数据为空或类型错误")
                
            # 移除空格并检查长度
            hex_str = response.replace(" ", "")
            if len(hex_str) < 43:
                raise ValueError(f"响应数据长度不足43字节: {len(hex_str)//2}字节")
            if len(hex_str) % 2 != 0:
                raise ValueError(f"响应数据长度无效: {len(hex_str)}字符")
            
            # 验证关键字段长度
            if len(hex_str) < 43:
                raise ValueError("响应数据不包含完整字段")
                
            # 解析各字段并验证
            result = {
                "复合应用类型": hex_str[0:2],
                "记录长度": hex_str[2:4],
                "应用锁定标志": hex_str[4:6],
                "出入口收费路网号": hex_str[6:10],
                "出入口收费站号": hex_str[10:14],
                "出入口收费车道号": hex_str[14:16],
                "出入口时间": hex_str[16:24],       # unix时间戳
                "车型": hex_str[24:26],
                "出入口状态": hex_str[26:28],
                "省内预留": hex_str[28:46],             # 保留字段
                "收费员工号": hex_str[46:52],
                "出入口班次": hex_str[52:54],
                "车牌号码": hex_str[54:78],
                "ETC预留": hex_str[78:86],
            }
            try:
                # 解析车牌号码
                bytes_obj = bytes.fromhex(hex_str[54:78].replace(" ", ""))
                string = bytes_obj.decode('gbk')
                result["车牌号码BCD"] =  string
            except Exception as e:
                logging.error(f"车牌号码解析失败: {str(e)}")
                result["车牌号码BCD"] = "解析失败"
            # 添加长度验证
            result["Length"] = len(hex_str)//2
            if isPrint:
                logging.info(f"0019响应解析结果: {result}")
            return result
            
        except ValueError as ve:
            logging.error(f"0019响应数据格式错误: {str(ve)}")
            return None
        except Exception as e:
            logging.error(f"解析0019响应失败: {str(e)}", exc_info=True)
            return None  

    def Parse_EF01(self, response, isPrint = False):
        """
        解析EF01响应数据
        :param response: 
        :return: 解析后的字段字典或None(解析失败时)
        返回字段说明:
        - Length: 总字节长度
        """
        try:
            if not response or not isinstance(response, str):
                raise ValueError("响应数据为空或类型错误")
                
            # 移除空格并检查长度
            hex_str = response.replace(" ", "")
            if len(hex_str) < 99:
                raise ValueError(f"EF01响应数据长度不足43字节: {len(hex_str)//2}字节")
            if len(hex_str) % 2 != 0:
                raise ValueError(f"EF01响应数据长度无效: {len(hex_str)}字符")
            
            # 验证关键字段长度
            if len(hex_str) < 99:
                raise ValueError("EF01响应数据不包含完整字段")
                
            # 解析各字段并验证
            result = {
                "发行方标识": hex_str[0:16],
                "协约类型": hex_str[16:18],
                "行业与省定义": hex_str[18:20],
                "合同序列号": hex_str[20:36],
                "合同签署日期": hex_str[36:44],
                "合同过期日期": hex_str[44:52],
                "拆卸状态": hex_str[52:54],  
                "预留": hex_str[54:],
            }
            # 添加长度验证
            result["Length"] = len(hex_str)//2
            if isPrint:
                logging.info(f"EF01响应解析结果: {result}")
            return result
            
        except ValueError as ve:
            logging.error(f"EF01响应数据格式错误: {str(ve)}")
            return None
        except Exception as e:
            logging.error(f"解析EF01响应失败: {str(e)}", exc_info=True)
            return None       
    
    def SetMMI(self, ConfirmMode = '01', mac = '02112233', control = '40', llc = '77', pdu_num = '91', action_req = '05', 
                DID = '01', action_type = '04', container_type = '1A', Paramter = '00'):
        logging.info("--------------设置MMI------------")
        mac = self.obu_mac_id
        if mac == "" or mac == None:
            mac = 'FF FF FF FF'
        data = f"{ConfirmMode} {mac} {control} {llc} {pdu_num} {action_req} {DID} {action_type} {container_type} {Paramter}"
        self.dsrc_frame.CMD = 0xF2
        self.dsrc_frame.DATA = bytes.fromhex(data)
        frame = self.dsrc_frame._build_frame()
        response =  self.send_command(frame.hex(" ").upper())
        return response

    def EventReport(self, ConfirmMode = '00', mac = '02112233', control = '40', llc = '03', pdu_num = '91', action_req = '60', 
                DID = '00', event_type = '00'):
        logging.info("--------------EventReport------------")
        mac = self.obu_mac_id
        data = f"{ConfirmMode} {mac} {control} {llc} {pdu_num} {action_req} {DID} {event_type}"
        self.dsrc_frame.CMD = 0xF2
        self.dsrc_frame.DATA = bytes.fromhex(data)
        frame = self.dsrc_frame._build_frame()
        self.send_command(frame.hex(" ").upper(), False)

    def generate_offset_and_length(self, max_length):
        """
        生成符合条件的偏移量和长度的十六进制字符串,确保偏移量 + 长度 <= 最大长度。

        :param max_length: 最大长度
        :return: 偏移量十六进制字符串和长度十六进制字符串
        """
        while True:
            length_hex = ''.join(random.choices('0123456789ABCDEF', k=2))
            length = int(length_hex, 16)
            offset_hex = ''.join(random.choices('0123456789ABCDEF', k=2))
            offset = int(offset_hex, 16)
            if offset + length <= max_length:
                return f"{offset_hex} {length_hex}"

    def parse_VST_str(self, VST_str):
        """
        解析 VST_str 字符串,按照给定的协议规则提取各个字段信息,以十六进制显示结果。

        :param VST_str: 待解析的十六进制字符串
        :return: 包含解析结果的字典
        """
        #result = {}
        result = OrderedDict()
        index = 14
        VST_str = VST_str.replace(' ', '')  # 去除空格

        if len(VST_str) > index:
            # 解析 MAC 地址
            mac_address = VST_str[index:index + 8]
            result['MAC地址'] = mac_address
            index += 8
        if len(VST_str) > index:
            # 解析 MAC 控制域
            mac_control = VST_str[index:index + 2]
            result['MAC控制域'] = f"0x{mac_control}"
            index += 2
        if len(VST_str) > index:
            # 解析 LLC 控制域
            llc_control = VST_str[index:index + 2]
            result['LLC控制域'] = f"0x{llc_control}"
            index += 2
        if len(VST_str) > index:
            # 解析字段字头
            field_header = VST_str[index:index + 2]
            result['字段字头'] = f"0x{field_header}"
            index += 2
        if len(VST_str) > index:
            # 解析 VST
            vst = VST_str[index:index + 2]
            result['VST'] = f"0x{vst}"
            index += 2
        if len(VST_str) > index:
            # 解析 Profile
            profile = VST_str[index:index + 2]
            config_number = (int(profile, 16) & 0x70) >> 4
            rf_channel = int(profile, 16) & 0x0F
            result['Profile配置号'] = f"0x{config_number:01x}"
            result['Profile射频通道号'] = f"0x{rf_channel:01x}"
            index += 2
        if len(VST_str) > index:
            # 解析 ApplicationList
            app_list = VST_str[index:index + 2]
            result['ApplicationList应用列表数'] = f"0x{app_list}"
            index += 2
        if len(VST_str) > index:
            # 解析 Aid
            aid = VST_str[index:index + 2]
            result['Aid'] = f"0x{aid}"
            index += 2
        if len(VST_str) > index:
            # 解析 Did
            did = VST_str[index:index + 2]
            result['Did'] = f"0x{did}"
            index += 2
        if len(VST_str) > index:
            # 解析 Option indicator
            option_indicator = VST_str[index:index + 2]
            result['RandOBE是否存在'] = bool(int(option_indicator, 16) & 0x80)
            result['privateInfo是否存在'] = bool(int(option_indicator, 16) & 0x40)
            result['gbICCInfo是否存在'] = bool(int(option_indicator, 16) & 0x20)
            result['Option indicator'] = f"0x{option_indicator}"
            index += 2

        # 解析 Parameter: SysInfoFile
        if len(VST_str) > index:
            sys_info_file = VST_str[index:index + 2]
            result['SysInfoFile Container Type'] = f"0x{sys_info_file}"
            index += 2
        if len(VST_str) > index:
            sys_info_file = VST_str[index:(index + 54)]
            result['SysInfoFile'] = f"0x{sys_info_file}"
            index += 54

        # 解析 Parameter: gbICCInfo
        if len(VST_str) > index:
            ContainerType = VST_str[index:index + 2]
            result['gbICCInfo Container Type'] = f"0x{ContainerType}"
            index += 2
        if len(VST_str) > index:
            ContainerLength = len(VST_str) - index - 16
            gbICCInfo = VST_str[index:index + ContainerLength]
            info_length = len(gbICCInfo)//2  # 解析信息长度
            result['gbICCInfoLength_int'] = f"{info_length}"  # 计算字节数
            result['gbICCInfoLength_hex'] = f"{info_length:02X}"  # 计算字节数
            result['gbICCInfo'] = f"0x{gbICCInfo}"
            index += ContainerLength

        # 解析 ObuConfiguration (MacID)
        if len(VST_str) > index:
            obu_mac_id = VST_str[index:(index + 8)]
            result['ObuConfiguration (MacID)'] = obu_mac_id
            index += 8

        # 解析 EquipmentClass
        if len(VST_str) > index:
            equipment_class = VST_str[index:index + 2]
            result['EquipmentClass是否支持IC卡接口'] = bool(int(equipment_class, 16) & 0x10)
            result['EquipmentClass'] = f"0x{equipment_class}"
            index += 2

        # 解析 EquipmentVersion
        if len(VST_str) > index:
            equipment_version = VST_str[index:index + 2]
            result['EquipmentVersion硬件版本'] = f"0x{equipment_version}"
            index += 2

        # 解析 ObuStatus
        if len(VST_str) > index:
            obu_status = VST_str[index:index + 2]
            obu_status_int = int(obu_status, 16)
            result['ObuStatus是否存在IC卡'] = not bool(obu_status_int & 0x80)
            icc_type = (obu_status_int & 0x70) >> 4
            icc_status = bool(obu_status_int & 0x08)
            is_locked = bool(obu_status_int & 0x04)
            is_tampered = bool(obu_status_int & 0x02)
            battery_status = bool(obu_status_int & 0x01)
            result['ObuStatus ICC类型'] = f"0x{icc_type:01x}"
            result['ObuStatus ICC状态'] = icc_status
            result['ObuStatus OBU是否被锁'] = is_locked
            result['ObuStatus OBU是否被拆动'] = is_tampered
            result['ObuStatus OBU电池状态'] = battery_status
            result['ObuStatus'] = f"0x{obu_status}"
            index += 2

        return result

    def _verify_bcc(self, frame_data):
        """BCC校验辅助方法"""
        bytearray_data = bytearray(frame_data)
        bcc = get_bcc(bytearray_data[2:-1])
        return bcc == frame_data[-1]

    def _log_communication(self, direction, data, start_time):
        """通信日志记录辅助方法"""
        time_str = get_time_str(start_time)
        delta_ms = get_delta_ms(start_time, time.time())
        log_msg = f"{time_str}[{delta_ms:3.0f}] {'Req' if direction == 'send' else 'Ack'}: {data}"
        logging.info(log_msg)

    def get_session_key(self, sm4_key = '00000000000000000000000000000000', c_rands='', consume_no='', payserial='', is_sm4=True):
        """
        生成会话密钥
        :param sm4_key: 密钥字节数组
        :param c_rands: 随机数字符串
        :param consume_no: 消费序号
        :param payserial: 支付流水号
        :param is_sm4: 是否使用SM4算法
        :return: 会话密钥字节数组
        """
        len_payserial = len(payserial)
        data = c_rands + consume_no + payserial[len_payserial-4:len_payserial]
        print(f"data = {data}")
        
        d = bytes.fromhex(data)
        
        if not is_sm4:
            # DES3加密
            from Cryptodome.Cipher import DES3
            cipher = DES3.new(sm4_key, DES3.MODE_ECB)
            sz_mac = cipher.encrypt(d)
            print(f"SM4 SessionKey = {sz_mac.hex().upper()}")
            return sz_mac
        else:
            # SM4加密
            in_bytes = bytearray(16)
            for n in range(8):
                in_bytes[n] = d[n]
            for n in range(8, 16):
                in_bytes[n] = ~d[n-8] & 0xff
                
            out2 = sm4_ecb_enc_dec(1, sm4_key, in_bytes.hex())
            print(f"SM4 SessionKey = {out2.upper()}")
            return out2

    def consume_soft_mac1(self, is_sm4 = True, sm4_key = '00000000000000000000000000000000', c_rands='', consume_no='', imoney='', timestamp='', psam_termid='', payserial=''):
        """
        软件计算MAC1
        :param is_sm4: 是否使用SM4算法
        :param sm4_key: 密钥字节数组
        :param c_rands: 随机数字符串
        :param consume_no: 消费序号
        :param imoney: 金额
        :param timestamp: 时间戳
        :param psam_termid: PSAM终端号
        :param payserial: 支付流水号
        :return: MAC计算结果
        """
        smt = imoney + "09" + psam_termid + timestamp
        print(f"数据 = {smt}")
        print(f"随机数 = {c_rands}")
        
        b = bytes.fromhex(smt)
        d = bytes.fromhex("00000000000000000000000000000000" if is_sm4 else "0000000000000000")
        
        session_key = self.get_session_key(sm4_key, c_rands, consume_no, payserial, is_sm4)
        print(f"key = {session_key.upper()}")
        
        if not is_sm4:
            # DES3 MAC计算
            from Cryptodome.Hash import CMAC
            from Cryptodome.Cipher import DES3
            cmac = CMAC.new(session_key, ciphermod=DES3)
            cmac.update(b)
            out = cmac.digest()[:8]
        else:
            # SM4 MAC计算
            out = sm4_mac(session_key, b.hex())
        
        print(f"mac = {out}")
        return out

    # 生成16进制随机字符串
    def get_rand_string(self, length=8):
        """
        生成16进制随机字符串
        :param length: 字符串长度(默认8)
        :return: 16进制随机字符串
        """
        return ''.join(random.choices('0123456789ABCDEF', k=length)).upper()

    def etc_transaction(self, iMoney = 0, is_sm4 = True, Entrance_Exit_Status = 0):
        """
        执行ETC交易流程
        包含BST发送、Secure获取、MAC验证等步骤
        """
        # 1. 发送BST并获取VST响应
        self.SendBST()
        self.PrintOBUMac()
        secure_response = self.ParseSecureResponse(self.GetSecure())
        
        # 2. 验证MAC
        des_key = "00000000000000000000000000000000"
        file_data = secure_response.get('File')
        des_data = sm4_ecb_enc_dec(0, des_key, file_data)
        
        # 优化日志记录
        debug_info = {
            'DES数据': des_data,
            '随机数': self.RndRSU,
            '随机数2': 'BEB4' + self.RndRSU[4:],
            'key': des_key,
            'MAC1': des_data[2:34]
        }
        logging.info(f"MAC验证阶段数据: {debug_info}")
        
        # 计算并验证MAC
        mac1 = des_data[2:34]
        mac2 = sm4_ecb_enc_dec(1, des_key, 'BEB4' + self.RndRSU[4:])
        mac_result = "相等" if mac1 == mac2 else "不相等"
        logging.info(f"MAC验证结果: {mac_result}")

        apdu1 = self.read_0015 + '2B' # 读取0015文件 2B长度，返回卡类型（1字节），IC卡ID（1字节），发卡运营商代码（2遍共8字节）
        apdu2 = self.read_0019 + '2B' # 读取0019文件 2B长度，返回0019内容
        apdu3 = self.read_0002 + '04' # 读取0002文件 04长度，返回卡余额
        test_str = self.TransferChannel( Apdus = (apdu1, apdu2, apdu3))  # 传输通道
        tran_resp = self.ParseTransferResponse(test_str)  # 解析响应
        # 3. 获取消费前余额
        card_balance_before_consumption = int(tran_resp.get('Apdu_2'), 16)  # 单位:分
        
        # 4. 消费初始化
        CONSUMPTION_AMOUNT = iMoney  # 消费金额(单位:分)
        imoney = f"{CONSUMPTION_AMOUNT:08X}"  # 8字节十六进制字符串
        KEY_ID = "42"  # 密钥ID
        apdu1 = f"805003020B{KEY_ID}{imoney}{self.psamCardId}"
        
       
        file_0019 = tran_resp.get('Apdu_1')  # 获取文件数据
        self.Parse_0019(file_0019)  # 解析0019文件
        # 修改0019文件的出入口状态
        strStatus = f"{Entrance_Exit_Status:02X}"
        file_0019 = file_0019[:26] + strStatus + file_0019[28:]  # 修改出入口状态
        apdu2 = self.write_0019 + file_0019[4:]  # 构造APDU2 写0019文件

        test_str = self.TransferChannel( Apdus = (apdu1, apdu2))  # 传输通道
        tran_resp = self.ParseTransferResponse(test_str)  # 解析响应
        logging.info('复合消费计算MAC1')
        consume_no = tran_resp.get('Apdu_0')[8:12]  # 获取消费流水号
        keyversion = tran_resp.get('Apdu_0')[18:20]  # 获取密钥版本号
        amarkt = tran_resp.get('Apdu_0')[20:22]  # 算法标识
        cRands = tran_resp.get('Apdu_0')[22:]  # 获取随机数
        timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")  # 获取时间戳(年份4位，其他2位)
        payserial = self.get_rand_string(8)  # 生成支付流水号
        logging.info(f"consume_no = {consume_no}, keyversion = {keyversion}, amarkt = {amarkt}")
        logging.info(f"cRands = {cRands}, timestamp = {timestamp}, payserial = {payserial}")  # 打印结果
        #消费软MAC1计算
        mac1 = self.consume_soft_mac1(is_sm4=is_sm4 ,c_rands=cRands, consume_no=consume_no, imoney=imoney, payserial=payserial, psam_termid=self.psamCardId, timestamp=timestamp)
        # 复合消费
        apdu1 = f"805401000F{payserial}{timestamp}{mac1}"  # 构造APDU1
        test_str = self.TransferChannel(Apdus=apdu1)       # 传输通道
        tran_resp = self.ParseTransferResponse(test_str)  # 解析响应
        tac = tran_resp.get('Apdu_0')[0:8]  # 获取TAC
        mac2 = tran_resp.get('Apdu_0')[8:]  # 获取MAC2
        logging.info(f"tac = {tac}, mac2 = {mac2}")  # 打印TAC 和 MAC2

        # 读取0002与0019文件
        apdu1 = self.read_0002 + '04'                           # 读取0002文件 04长度，返回卡余额
        apdu2 = self.read_0019 + '2B'                           # 读取0019文件 2B长度，返回0019内容
        test_str = self.TransferChannel(Apdus=(apdu1, apdu2))   # 传输通道
        tran_resp = self.ParseTransferResponse(test_str)        # 解析响应
        card_balance = int(tran_resp.get('Apdu_0'), 16)         # 获取卡余额
        str_0019 = self.Parse_0019(tran_resp.get('Apdu_1'))     # 解析0019文件
        logging.info(f"出入口状态: {str_0019.get('出入口状态')}") # 打印出入口状态

        # 记录交易结果
        transaction_result = {
            '消费前余额': card_balance_before_consumption,
            '消费后余额': card_balance,
            '实际消费金额': card_balance_before_consumption - card_balance
        }
        logging.info(f"交易结果: {transaction_result} 分")
        
        # 5. 设置MMI并上报事件
        self.SetMMI()
        self.EventReport()

    def sm4_mac(self, key, data, iv='0' * 32):
        """
        计算SM4 MAC
        :param key: 密钥
        :param data: 数据
        :param iv: 初始向量
        :return: MAC值
        """
        out = sm4_mac(key, data, iv)
        return out

    # 写OBU防拆位
    def write_obu_tamper(self, tamper_status):
        """
        写OBU防拆位
        参数:
            tamper_status: 防拆状态值(0-255)
        返回:
            操作结果(True/False)
        """
        try:
            # 1. 发送BST建立通信
            logging.info("开始设置OBU防拆位...")
            self.SendBST()
            
            # 2. 发送APDU命令获取随机数
            apdu_commands = (
                '00A40000023F00',  # 进入3F00目录
                '00B0810A08',       # 选择文件
                '0084000008'        # 获取8字节随机数
            )
            response = self.TransferChannel(llc="F7", channelID="02", Apdus=apdu_commands)
            
            # 3. 解析响应并处理随机数
            obu_rand = self.ParseTransferResponse(response).get('Apdu_2')
            if not obu_rand:
                raise ValueError("获取随机数失败")
                
            obu_rand = obu_rand.ljust(32, '0')  # 填充到32位
            logging.info(f"获取OBU随机数: {obu_rand}")
            
            # 4. 构造防拆状态数据并计算MAC
            tamper_status_str = f'{tamper_status:02X}'
            data = f"04D6811A05{tamper_status_str}"
            mac = sm4_mac("00000000000000000000000000000000", data, obu_rand)
            logging.info(f"计算SM4 MAC: {mac}")
            
            # 5. 发送防拆状态命令
            apdu_command = data + mac
            response = self.TransferChannel(llc="F7", channelID="02", Apdus=apdu_command)
            test_str = self.ParseTransferResponse(response)
            if test_str.get('Sw12_0') != "9000":
                self.SetMMI()
                self.EventReport()
                logging.error(f"设置OBU防拆位失败: {test_str.get('Sw12_0')}")
                return False

            # 6. 发送APDU命令获取防拆状态
            apdu_commands = (
                '00A40000023F00',  # 进入3F00目录
                '00B0811A01',      # 获取EFO1文件 防拆状态
            )
            response = self.TransferChannel(llc="F7", channelID="02", Apdus=apdu_commands)
            test_str = self.ParseTransferResponse(response)
            if test_str.get('Sw12_0')!= "9000":
                self.SetMMI()
                self.EventReport()
                logging.error(f"获取OBU防拆位失败: {test_str.get('Sw12_0')}")
                return False
            # 7. 解析防拆状态
            obu_tamper_status = test_str.get('Apdu_1')

            # 8. 设置MMI和事件报告
            self.SetMMI()
            self.EventReport()

            # 9. 输出结果
            if obu_tamper_status != tamper_status_str:
                logging.error(f"设置OBU防拆位失败: 实际状态为 {obu_tamper_status}, 预期状态为 {tamper_status_str}")
                return False
            
            logging.info(f"成功设置OBU防拆位为: {tamper_status_str}")
            return True
            
        except Exception as e:
            logging.error(f"设置OBU防拆位失败: {str(e)}")
            return False

    def read_obu_tamper(self):
        """
        读OBU防拆位
        返回:
            返回防拆状态值
        """
        try:
            # 1. 发送BST建立通信
            logging.info("开始读取OBU防拆位...")
            self.SendBST()
            # 2. 发送APDU命令获取防拆状态
            apdu_commands = (
                '00A40000023F00',  # 进入3F00目录
                '00B0811A01',      # 获取防拆状态
            )
            response = self.TransferChannel(llc="F7", channelID="02", Apdus=apdu_commands)
            test_str = self.ParseTransferResponse(response)
            if test_str.get('Sw12_0')!= "9000":
                self.SetMMI()
                self.EventReport()
                logging.error(f"获取OBU防拆位失败: {test_str.get('Sw12_0')}")
                return 8
            # 3. 解析防拆状态
            obu_tamper_status = test_str.get('Apdu_1')

            # 4. 设置MMI和事件报告
            self.SetMMI()
            self.EventReport()

            logging.info(f"OBU防拆位为: {obu_tamper_status}")
            return obu_tamper_status
            
        except Exception as e:
            logging.error(f"读取OBU防拆位失败: {str(e)}")
            return False


# 示例用法
if __name__ == "__main__":
    try:
        m600 = M600Communicator('com8')
        m600.InitRSU()
        m600.write_obu_tamper(0)
        m600.close()
    except Exception as e:
        logging.error(f"An error occurred: {e}")


